/**
 *    title:  ftpclient.c
 *    author:  sleeeeeping
 *    created:  2024-08-21 10:06:18
**/
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include "ftpclient.h"
#include "tools.h"

Command cmds[] = {
    {"ls", lsFtpClient},
    {"pwd", pwdFtpClient},
    {"cd", cdFtpClient},
    {"mkdir", mkdirFtpClient},
    {"rm", rmFtpClient},
    {"put", putFtpClient},
    {"get", getFtpClient},
    {"bye", byeFtpClient}
};
size_t cmdCnt = sizeof(cmds) / sizeof(cmds[0]);

static int _readToWrite(int srcfd, int destfd) {
    char buf[BUFSIZ << 4];
    int ret = 0, sum = 0;
    while ((ret = read(srcfd, buf, BUFSIZ << 4)) > 0) {
        write(destfd, buf, ret);
        sum += ret;
    }
    return sum;
}

static int _sendCmd(FTPClient *ftp) {
    time_t t = time(NULL);
    struct tm *pt = localtime(&t);
    printf("[%02d:%02d:%02d] [R]:%s", 
                                    pt->tm_hour, 
                                    pt->tm_min, 
                                    pt->tm_sec, 
                                    ftp->sbuf);
    return sendNetWork(ftp->cnw, ftp->sbuf, strlen(ftp->sbuf));
} 

static int _recvResult(FTPClient *ftp) {
    char *tmp = ftp->rbuf;
    do {
        if (recvNetWork(ftp->cnw, tmp, 1) != 1) {
            break;
        }
    } while (*tmp != '\n' && tmp++);
    *tmp = '\0';
    time_t t = time(NULL);
    struct tm *pt = localtime(&t);
    printf("\033[01;32m[%02d:%02d:%02d] [R]:%s\033[00m\n",
                                                    pt->tm_hour, 
                                                    pt->tm_min, 
                                                    pt->tm_sec, 
                                                    ftp->rbuf);
    int code;
    sscanf(ftp->rbuf, "%d", &code);
    return code;
}

// 连接服务器
FTPClient *createFtpClient(const char *ip, unsigned short port) {
    FTPClient *ftp = malloc(sizeof(FTPClient));
    if (ftp == NULL) {
        return NULL;
    }
    printf("Connected to %s.\n", ip);
    ftp->cnw = createNetWork(SOCK_STREAM, ip, port, false);
    if (ftp->cnw == NULL) {
        return NULL;
    }
    if (_recvResult(ftp) != 220) {
        closeNetWork(ftp->cnw);
        destroyFtpClient(ftp);
        return NULL;
    }

    return ftp;
}

int userFtpClient(FTPClient *ftp, const char *args) {
    sprintf(ftp->sbuf, "USER %s\n", args);
    _sendCmd(ftp);
    if (_recvResult(ftp) != 331) {
        return -1;
    } else {
        return 0;
    }
}

int passFtpClient(FTPClient *ftp, const char *args) {
    sprintf(ftp->sbuf, "PASS %s\n", args);
    _sendCmd(ftp);
    if (_recvResult(ftp) != 230) {
        return -1;
    } else {
        return 0;
    }
}

#define IP_LEN 20

static int _pasvFtpClient(FTPClient *ftp) {
    sprintf(ftp->sbuf, "PASV\n");
    _sendCmd(ftp);
    if (_recvResult(ftp) != 227) {
        return -1;
    }
    unsigned char ip1, ip2, ip3, ip4, port1, port2;
    sscanf(strchr(ftp->rbuf, '(') + 1, "%hhu,%hhu,%hhu,%hhu,%hhu,%hhu", &ip1, &ip2, &ip3, &ip4, &port1, &port2);
    char ip[IP_LEN];
    sprintf(ip, "%hhu.%hhu.%hhu.%hhu", ip1, ip2, ip3, ip4);
    ftp->dnw = createNetWork(SOCK_STREAM, ip, port1 * 256 + port2, false);
    if (ftp->dnw == NULL) {
        return -2;
    } else {
        return 0;
    }
}

int lsFtpClient(FTPClient *ftp, const char *args) {
    if (_pasvFtpClient(ftp) < 0) {
        return -1;
    }
    sprintf(ftp->sbuf, "LIST -al\n");
    _sendCmd(ftp);
    if (_recvResult(ftp) != 150) {
        return -2;
    }
    _readToWrite(ftp->dnw->sockfd, 1);
    closeNetWork(ftp->dnw);
    if (_recvResult(ftp) != 226) {
        return -3;
    }
    return 0;
}

int pwdFtpClient(FTPClient *ftp, const char *args) {
    sprintf(ftp->sbuf, "PWD\n");
    _sendCmd(ftp);
    if (_recvResult(ftp) != 257) {
        return -1;
    } else {
        return 0;
    }
}

int cdFtpClient(FTPClient *ftp, const char *args) {
    sprintf(ftp->sbuf, "CWD\n");
    _sendCmd(ftp);
    if (_recvResult(ftp) != 250) {
        return -1;
    } else {
        return 0;
    }
}

int mkdirFtpClient(FTPClient *ftp, const char *args) {
    sprintf(ftp->sbuf, "MKD\n");
    _sendCmd(ftp);
    if (_recvResult(ftp) != 257) {
        return -1;
    } else {
        return 0;
    }
}

int rmFtpClient(FTPClient *ftp, const char *args) {
    sprintf(ftp->sbuf, "RMD\n");
    _sendCmd(ftp);
    if (_recvResult(ftp) != 257) {
        return -1;
    } else {
        return 0;
    }
}

int putFtpClient(FTPClient *ftp, const char *args) {
    sprintf(ftp->sbuf, "TYPE A\n");
    _sendCmd(ftp);
    if (_recvResult(ftp) != 200) {
        return -1;
    }
    if (_pasvFtpClient(ftp)) {
        return -2;
    }
    sprintf(ftp->sbuf, "STOR%s\n", args);
    _sendCmd(ftp);
    if (_recvResult(ftp) != 150) {
        return -3;
    }
    int fd = open(args + 1, O_RDONLY);
    if (fd < 0) {
        return -4;
    }
    _readToWrite(fd, ftp->dnw->sockfd);
    closeNetWork(ftp->dnw);
    close(fd);
    if (_recvResult(ftp) != 226) {
        return -5;
    }
    return 0;
}

int getFtpClient(FTPClient *ftp, const char *args) {
    sprintf(ftp->sbuf, "TYPE A\n");
    _sendCmd(ftp);
    if (_recvResult(ftp) != 200) {
        return -1;
    }
    if (_pasvFtpClient(ftp) < 0) {
        return -2;
    }
    sprintf(ftp->sbuf, "RETR%s\n", args);
    _sendCmd(ftp);
    if (_recvResult(ftp) != 150) {
        return -3;
    }
    int fd = open(args + 1, O_WRONLY|O_CREAT, 0644);
    if (fd < 0) {
        return -4;
    }
    _readToWrite(ftp->dnw->sockfd, fd);
    closeNetWork(ftp->dnw);
    close(fd);
    if (_recvResult(ftp) != 226) {
        return -5;
    }
    return 0;
}

int byeFtpClient(FTPClient *ftp, const char *args) {
    destroyFtpClient(ftp);
    exit(0);
}

int destroyFtpClient(FTPClient *ftp) {
    closeNetWork(ftp->cnw);
    free(ftp);
    return 0;
}